Skip to content

1.5.0 - #63

Merged
Alex-GF merged 18 commits into
mainfrom
develop
Jul 31, 2026
Merged

1.5.0#63
Alex-GF merged 18 commits into
mainfrom
develop

Conversation

@Alex-GF

@Alex-GF Alex-GF commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

No description provided.

Alex-GF and others added 17 commits May 14, 2026 12:27
0.10.3 stops one version short of the current specification:

  0.10.3: PRICING2YAML_VERSIONS = ["1.0","1.1","2.0","2.1","3.0"]
  0.11.1: PRICING2YAML_VERSIONS = ["1.0","1.1","2.0","2.1","3.0","3.1"]

so registering a pricing written against 3.1 is refused with "Unsupported
version: 3.1", and the only way to register anything is to declare 3.0 and stay
a version behind.

No source change was needed: the imports SPACE uses - retrievePricingFromPath,
retrievePricingFromText, and the Pricing/Plan/Feature/UsageLimit types - kept
their signatures across the two releases. tsc passes and the API suite passes
against a local MongoDB and Redis.
Authentication reads the database, so anything that can go wrong with the
database surfaces as an exception in the auth middleware - and the catch turned
every one of them into 401 with the driver's message as the body:

    401 {"error":"connect ECONNREFUSED 127.0.0.1:27017"}
    401 {"error":"Operation `users.findOne()` buffering timed out after 10000ms"}

The status says the caller sent a bad key. It sends whoever is debugging to
look at credentials that were never judged, because the request never got far
enough to judge them.

The healthcheck agreed: it returned 200 unconditionally, proving only that the
HTTP listener was up. A SPACE whose MongoDB container had stopped reported
healthy for fifteen hours while refusing every authenticated request as
unauthorised. Two signals agreeing on the wrong answer is what made this
expensive.

authenticateUserApiKey and authenticateOrgApiKey now throw a typed
InvalidApiKeyError for the two cases that really are bad credentials. The catch
answers 401 only for those; anything else is 503 with Retry-After, since the
credential was never judged and the caller should try again.

The healthcheck reports 503 when Mongoose is not connected, and pings the
database when it is - readyState is what the driver believes, a ping is what
the database says, and they disagree when a connection has gone stale.

Verified by stopping the database under a running server:

    main:      healthcheck 200, authed call 401 {"error":"connect ECONNREFUSED…"}
    this PR:   healthcheck 503 {"database":"disconnected"}
               authed call 503 {"error":"Space cannot verify credentials right now."}
An evaluation that touches two usage limits recorded one of them.

_applyExpectedConsumption reads the whole contract, increments a single usage
level in its own copy, and writes the whole contract back. evaluateFeature
called it once per limit through Promise.all, so every call read the same
starting state and only the last write survived - a lost update, silent, on the
path that decides whether somebody may use a feature.

_applyExpectedConsumptions applies every limit to one contract copy and writes
once: correct, and one round trip instead of one per limit. Limits are all
validated before anything is written, so naming a limit that does not exist
cannot leave the others half-applied. The single-limit method stays, delegating.

Seven tests against stubbed repository and cache, including one that pins why
the batch method exists: calling the single-limit method concurrently is still
lossy, because read-modify-write on a whole document cannot be made safe by
calling it more carefully. Making that safe means an atomic $inc in the
repository, which is a larger change than this one.

Their feature-evaluation suite (26 tests) passes unchanged.
Two falsy checks treated a numeric zero as an absent value, so a caller who
named a limit and declared it costs nothing was told they had not named it
at all. The same check on the sum also refused an ordinary positive
consumption whenever the usage level was still zero, which is the state of
every contract on its first call and after every renewal.
The job reads the Mongo port and database name from the `testing`
environment, which GitHub withholds from fork pull requests. Both resolved
to an empty string, so the action was asked to publish port `` and gave
docker `-p :`, which it rejects before any test runs.

Defaults now stand in when the environment is absent. They are the only
values that can work, since MONGO_URI already hard-codes 27017 and
space_testing_db.
Narrowing the catch to a dedicated error type was not enough on its own:
UserService.findByApiKey reports an unknown key by throwing rather than by
returning nothing, so the commonest 401 in the suite was being answered 503.

The lookup is now wrapped in one place that tells the codebase's own
INVALID DATA: signal - a caller's mistake - apart from anything else, which
is the database being unable to answer. Both directions are pinned by tests.
Let the integration tests run on pull requests from forks
…-401

A database outage should not be reported as 401 Unauthorized
Bump pricing4ts to ^0.11.1 so Pricing2Yaml 3.1 can be registered
Batching a whole evaluation into one read and one write stopped an
evaluation from losing its own limits, but not two requests from losing each
other's: both read the same consumed value, both write the same total, and
one consumption disappears.

The increment is now handed to Mongo as $inc and evaluated against the
stored document, so concurrent calls compose. The filter requires every
usage level to exist, which keeps validation and the write in one operation
instead of leaving a window between them.
Apply expected consumption for all limits in one write
@Alex-GF
Alex-GF deployed to testing July 31, 2026 11:09 — with GitHub Actions Active
@Alex-GF
Alex-GF merged commit 79aea10 into main Jul 31, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants